﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;

namespace TestBlockCanvas
{
    // Converter for subtracting a parameter value from a double
    public class SubtractConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double baseValue && parameter != null)
            {
                if (double.TryParse(parameter.ToString(), out double subtractValue))
                {
                    return Math.Max(0, baseValue - subtractValue);
                }
            }
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Not implemented as bindings are one-way
            throw new NotImplementedException();
        }
    }

    // Converter for adding a parameter value to a double
    public class DragCanvasConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is double baseValue && parameter != null)
            {
                if (double.TryParse(parameter.ToString(), out double addValue))
                {
                    return baseValue + addValue;
                }
            }
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Not implemented as bindings are one-way
            throw new NotImplementedException();
        }
    }

    public partial class MainWindow : Window
    {
        private FrameworkElement _dragControl = null; // The control currently being dragged
        private Point _dragOffset; // Mouse position relative to the dragged control's top-left corner
        private bool _isDragging = false; // Flag to indicate if a control is being dragged
        private const double EdgeMargin = 100; // Minimum distance from canvas edge before expanding

        private bool _isPanning = false; // Flag to indicate if the canvas is being panned
        private Point _lastMousePositionForPanning; // Last mouse position during panning

        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Handles mouse down events on the SourcePanel to initiate cloning and dragging.
        /// </summary>
        private void SourcePanel_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                FrameworkElement originalSource = e.OriginalSource as FrameworkElement;
                if (originalSource == null) return;

                // Traverse up the visual tree to find the actual control within the StackPanel
                // that was clicked (e.g., Button, Border, TextBlock)
                FrameworkElement actualSourceControl = originalSource;
                while (actualSourceControl != null && actualSourceControl.Parent != sender)
                {
                    actualSourceControl = VisualTreeHelper.GetParent(actualSourceControl) as FrameworkElement;
                }

                // Ensure a draggable control was clicked, not the StackPanel background or title TextBlock
                if (actualSourceControl != null && actualSourceControl != sender &&
                    !(actualSourceControl is TextBlock tb && tb.Text == "源控件："))
                {
                    // Calculate the position where the new control should appear in DragCanvas
                    Point originalControlCanvasPos = CalculatePositionInDragCanvas(actualSourceControl);
                    // Calculate the offset of the mouse within the *original* control
                    _dragOffset = e.GetPosition(actualSourceControl);

                    // Clone the clicked control
                    FrameworkElement newControl = CloneFrameworkElement(actualSourceControl);
                    if (newControl != null)
                    {
                        DragCanvas.Children.Add(newControl);

                        // Set the initial position of the cloned control
                        Canvas.SetLeft(newControl, originalControlCanvasPos.X);
                        Canvas.SetTop(newControl, originalControlCanvasPos.Y);

                        // Wait for layout to ensure the control has its ActualWidth/Height
                        // before starting the drag operation correctly
                        void LayoutHandler(object s, EventArgs args)
                        {
                            DragCanvas.LayoutUpdated -= LayoutHandler; // Unsubscribe to prevent multiple calls

                            _isDragging = true;
                            _dragControl = newControl;
                            _dragControl.CaptureMouse(); // Capture mouse for the cloned control to ensure consistent events
                            e.Handled = true; // Mark event as handled to prevent canvas panning from starting
                        }

                        DragCanvas.LayoutUpdated += LayoutHandler;
                        e.Handled = true; // Also handle it here to prevent immediate canvas panning before layout
                    }
                }
            }
        }

        /// <summary>
        /// Calculates the position of a control in the DragCanvas's coordinate system.
        /// </summary>
        private Point CalculatePositionInDragCanvas(FrameworkElement control)
        {
            try
            {
                // Preferred method: Transform directly to the DragCanvas's coordinate system
                GeneralTransform transform = control.TransformToVisual(DragCanvas);
                return transform.Transform(new Point(0, 0));
            }
            catch
            {
                // Fallback method if direct transformation fails (e.g., control not yet in visual tree)
                // Get position relative to MainCanvas, then subtract DragCanvas's offset within MainCanvas
                Point posInMainCanvas = control.TransformToVisual(MainCanvas).Transform(new Point(0, 0));
                Point dragCanvasPosInMainCanvas = RightBorder.TransformToVisual(MainCanvas).Transform(new Point(0, 0));
                return new Point(posInMainCanvas.X - dragCanvasPosInMainCanvas.X, posInMainCanvas.Y - dragCanvasPosInMainCanvas.Y);
            }
        }

        /// <summary>
        /// Creates a clone of a FrameworkElement, copying common properties and content.
        /// </summary>
        private FrameworkElement CloneFrameworkElement(FrameworkElement original)
        {
            FrameworkElement cloned = Activator.CreateInstance(original.GetType()) as FrameworkElement;
            if (cloned == null) return null;

            // Copy common FrameworkElement properties
            cloned.Width = original.Width;
            cloned.Height = original.Height;
            cloned.MinWidth = original.MinWidth;
            cloned.MinHeight = original.MinHeight;
            cloned.MaxWidth = original.MaxWidth;
            cloned.MaxHeight = original.MaxHeight;
            cloned.Margin = new Thickness(0); // Clear margin for positioning on Canvas

            // Copy Control specific properties
            if (original is Control originalControl && cloned is Control clonedControl)
            {
                clonedControl.Background = originalControl.Background;
                clonedControl.BorderBrush = originalControl.BorderBrush;
                clonedControl.BorderThickness = originalControl.BorderThickness;
                clonedControl.Padding = originalControl.Padding;
                // For Button, copy Content (if string)
                if (originalControl is Button originalButton && clonedControl is Button clonedButton)
                {
                    if (originalButton.Content is string contentString)
                    {
                        clonedButton.Content = contentString;
                    }
                }
            }
            // Copy Border specific properties and its child
            else if (original is Border originalBorder && cloned is Border clonedBorder)
            {
                clonedBorder.Background = originalBorder.Background;
                clonedBorder.BorderBrush = originalBorder.BorderBrush;
                clonedBorder.BorderThickness = originalBorder.BorderThickness;
                clonedBorder.Padding = originalBorder.Padding;
                if (originalBorder.Child is UIElement originalChild)
                {
                    clonedBorder.Child = CloneFrameworkElement(originalChild as FrameworkElement); // Recursively clone child
                }
            }
            // Copy TextBlock specific properties
            else if (original is TextBlock originalTextBlock && cloned is TextBlock clonedTextBlock)
            {
                clonedTextBlock.Text = originalTextBlock.Text;
                clonedTextBlock.Background = originalTextBlock.Background;
                clonedTextBlock.Foreground = originalTextBlock.Foreground;
                clonedTextBlock.FontSize = originalTextBlock.FontSize;
                clonedTextBlock.FontWeight = originalTextBlock.FontWeight;
            }

            // Attach PreviewMouseDown to allow dragging the cloned control
            cloned.PreviewMouseDown += DragControl_PreviewMouseDown;

            return cloned;
        }

        /// <summary>
        /// Global MouseMove handler for the MainCanvas (captures both control drag and canvas pan).
        /// </summary>
        private void MainCanvas_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging && _dragControl != null && e.LeftButton == MouseButtonState.Pressed)
            {
                // Calculate new position for the dragged control
                Point currentPoint = e.GetPosition(DragCanvas);
                double newX = currentPoint.X - _dragOffset.X;
                double newY = currentPoint.Y - _dragOffset.Y;

                Canvas.SetLeft(_dragControl, newX);
                Canvas.SetTop(_dragControl, newY);
                e.Handled = true; // Mark as handled to prevent panning simultaneously
            }
            else if (_isPanning && e.LeftButton == MouseButtonState.Pressed)
            {
                // Calculate scroll amount based on mouse movement
                Point currentMousePosition = e.GetPosition(RightBorder); // Get position relative to ScrollViewer
                double deltaX = currentMousePosition.X - _lastMousePositionForPanning.X;
                double deltaY = currentMousePosition.Y - _lastMousePositionForPanning.Y;

                // Adjust ScrollViewer offsets
                RightBorder.ScrollToHorizontalOffset(RightBorder.HorizontalOffset - deltaX);
                RightBorder.ScrollToVerticalOffset(RightBorder.VerticalOffset - deltaY);

                _lastMousePositionForPanning = currentMousePosition; // Update last position for next move
                e.Handled = true; // Mark event as handled
            }
        }

        /// <summary>
        /// Global MouseUp handler for the MainCanvas (releases control drag or canvas pan).
        /// </summary>
        private void MainCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (_isDragging && _dragControl != null)
            {
                // Get the release point in the MainCanvas's coordinates
                Point releasePoint = e.GetPosition(MainCanvas);

                // Define the bounds of the SourcePanel in MainCanvas coordinates
                Rect sourcePanelBounds = new Rect(
                    Canvas.GetLeft(SourcePanel),
                    Canvas.GetTop(SourcePanel),
                    SourcePanel.ActualWidth,
                    SourcePanel.ActualHeight
                );

                // If control is dropped back into SourcePanel, remove it
                if (sourcePanelBounds.Contains(releasePoint))
                {
                    DragCanvas.Children.Remove(_dragControl);
                }
                else
                {
                    // Otherwise, check and expand canvas if near edge
                    ExpandCanvasIfNearEdge(_dragControl);
                }

                _dragControl.ReleaseMouseCapture(); // Release mouse capture from the dragged control
                _isDragging = false;
                _dragControl = null;
                e.Handled = true; // Mark event as handled
            }
            else if (_isPanning)
            {
                RightBorder.ReleaseMouseCapture(); // Release mouse capture from the ScrollViewer
                _isPanning = false;
                e.Handled = true; // Mark event as handled
            }
        }

        /// <summary>
        /// Checks if a control is near the canvas edges and expands the canvas if needed.
        /// Also adjusts existing control positions if expansion is to the left or top.
        /// </summary>
        private void ExpandCanvasIfNearEdge(FrameworkElement control)
        {
            // Get control's current position and size
            double left = Canvas.GetLeft(control);
            double top = Canvas.GetTop(control);
            double width = control.ActualWidth;
            double height = control.ActualHeight;
            double right = left + width;
            double bottom = top + height;

            // Get current DragCanvas dimensions
            double canvasWidth = DragCanvas.Width;
            double canvasHeight = DragCanvas.Height;

            double expandRight = 0;
            double expandBottom = 0;
            double expandLeft = 0;
            double expandTop = 0;

            // Calculate needed expansion for each edge
            if (canvasWidth - right < EdgeMargin)
            {
                expandRight = EdgeMargin - (canvasWidth - right);
            }
            if (canvasHeight - bottom < EdgeMargin)
            {
                expandBottom = EdgeMargin - (canvasHeight - bottom);
            }
            if (left < EdgeMargin)
            {
                expandLeft = EdgeMargin - left;
            }
            if (top < EdgeMargin)
            {
                expandTop = EdgeMargin - top;
            }

            // If no expansion is needed, return
            if (expandRight <= 0 && expandBottom <= 0 && expandLeft <= 0 && expandTop <= 0)
                return;

            // Calculate new canvas dimensions
            double newWidth = canvasWidth + expandRight + expandLeft;
            double newHeight = canvasHeight + expandBottom + expandTop;

            // If expanding to the left or top, shift all existing controls
            if (expandLeft > 0 || expandTop > 0)
            {
                foreach (UIElement child in DragCanvas.Children)
                {
                    if (child is FrameworkElement element)
                    {
                        double childLeft = Canvas.GetLeft(element);
                        double childTop = Canvas.GetTop(element);

                        if (expandLeft > 0)
                        {
                            Canvas.SetLeft(element, childLeft + expandLeft);
                        }
                        if (expandTop > 0)
                        {
                            Canvas.SetTop(element, childTop + expandTop);
                        }
                    }
                }
            }

            // Update DragCanvas dimensions
            DragCanvas.Width = newWidth;
            DragCanvas.Height = newHeight;

            // Scroll to keep the dropped control in view
            ScrollToControl(control);
        }

        /// <summary>
        /// Scrolls the ScrollViewer to ensure the specified control is visible,
        /// ideally centered if possible.
        /// </summary>
        private void ScrollToControl(FrameworkElement control)
        {
            try
            {
                double left = Canvas.GetLeft(control);
                double top = Canvas.GetTop(control);
                double centerX = left + control.ActualWidth / 2;
                double centerY = top + control.ActualHeight / 2;

                // Calculate scroll position (center the control in the viewport)
                double scrollX = Math.Max(0, centerX - RightBorder.ViewportWidth / 2);
                double scrollY = Math.Max(0, centerY - RightBorder.ViewportHeight / 2);

                RightBorder.ScrollToHorizontalOffset(scrollX);
                RightBorder.ScrollToVerticalOffset(scrollY);
            }
            catch
            {
                // Ignore potential errors during scrolling (e.g., if control is removed quickly)
            }
        }

        /// <summary>
        /// Handles mouse down on a cloned control within the DragCanvas,
        /// initiating the drag operation for that specific control.
        /// </summary>
        private void DragControl_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (sender is FrameworkElement elementToDrag)
                {
                    _isDragging = true;
                    _dragControl = elementToDrag;
                    _dragOffset = e.GetPosition(_dragControl); // Offset of mouse within the control
                    _dragControl.CaptureMouse(); // Capture mouse for *this specific control*
                    e.Handled = true; // IMPORTANT: Mark event as handled to stop propagation and prevent canvas panning
                }
            }
        }

        /// <summary>
        /// Sets initial scroll position of the RightBorder ScrollViewer on load.
        /// </summary>
        private void RightBorder_Loaded(object sender, RoutedEventArgs e)
        {
            var sv = RightBorder;
            // Scroll to roughly 20% into the content if scrollable
            if (sv.ScrollableWidth > 0)
                sv.ScrollToHorizontalOffset(sv.ScrollableWidth * 0.2);
            if (sv.ScrollableHeight > 0)
                sv.ScrollToVerticalOffset(sv.ScrollableHeight * 0.2);
        }

        /// <summary>
        /// Handles mouse down on the DragCanvas background to initiate panning.
        /// </summary>
        private void DragCanvas_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            // Only start panning if:
            // 1. A control is NOT currently being dragged (_isDragging is false)
            // 2. The left mouse button is pressed
            // 3. The mouse click originated DIRECTLY on the DragCanvas background (not on a child control)
            if (!_isDragging && e.LeftButton == MouseButtonState.Pressed && e.OriginalSource == DragCanvas)
            {
                _isPanning = true;
                // Store initial mouse position relative to the ScrollViewer's viewport
                _lastMousePositionForPanning = e.GetPosition(RightBorder);
                RightBorder.CaptureMouse(); // Capture mouse for the ScrollViewer to ensure continuous events
                e.Handled = true; // Mark event as handled to prevent any other elements from reacting
            }
        }


        /// <summary>
        /// 最大画面尺寸改变后，调整布局
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Console.WriteLine("最大画布。。。尺寸有变化 。。。。");
            //取消其余的尺寸绑定。。。。。
            Console.WriteLine(MainCanvas.ActualWidth);
            Console.WriteLine(MainCanvas.ActualHeight);
            Console.WriteLine(MainGrid.ActualWidth);
            Console.WriteLine(MainGrid.ActualHeight);
            //1。调整控件仓库布局
            //尺寸与左上
            /*
             * 
                        Canvas.Left="0"
                        Canvas.Top="0"
             */
            SourcePanel.Width = 256;
            SourcePanel.Height = MainCanvas.ActualHeight;
            Canvas.SetLeft(SourcePanel, 0);
            Canvas.SetTop(SourcePanel, 0);

            //2。调整绘制区容器布局
            RightBorder.Width = MainCanvas.ActualWidth - 256;
            RightBorder.Height = MainCanvas.ActualHeight;
            Canvas.SetLeft(RightBorder, 256);
            Canvas.SetTop(RightBorder, 0);

            //3。调整绘制区布局
            if (DragCanvas.Width < RightBorder.Width + 200)
            {
                DragCanvas.Width = RightBorder.Width + 200;
            }
            if (DragCanvas.Height < RightBorder.Height + 200)
            {
                DragCanvas.Height = RightBorder.Height + 200;
            }
            

        }
    }
}